package org.matchit.converter;

import org.springframework.core.convert.converter.Converter;

/**
 * Enables converter instances to be chained in a specific "flow".
 * Usage of the flow converter goes as follows:
 * 
 * <pre>
 *  birthDateAsString = flow(property("birthDate")).then(toString())
 * </pre>
 * 
 * @author Jeroen van Schagen
 * @since 12-04-2011
 *
 * @param <S> type of source elements
 * @param <T> type of target elements
 */
public abstract class FlowConverter<S, T> implements Converter<S, T> {

    /**
     * Construct a new {@link FlowConverter}.
     * @param <S> type of source elements
     * @param <T> type of target elements
     * @param delegate initial converter
     * @return new flow converter instance
     */
    public static <S, T> FlowConverter<S, T> flow(Converter<S, T> delegate) {
        return new SingleFlowConverter<S, T>(delegate);
    }

    /**
     * Build a new converter, which combines the conversion logic of our
     * current converter and the newly provided converter instance.
     * @param <X> new type of target elements
     * @param rhs new converter to be chained behind our current converter
     * @return new flow converter, with combined conversion logic
     */
    public <X> FlowConverter<S, X> then(Converter<T, X> rhs) {
        return new CompositeFlowConverter<S, T, X>(this, rhs);
    }

    /**
     * Flow converter that consists of only one step.
     * 
     * @param <S> type of source elements
     * @param <T> type of target elements
     */
    private static class SingleFlowConverter<S, T> extends FlowConverter<S, T> {
        private final Converter<S, T> delegate;

        // Default constructor (hidden)
        private SingleFlowConverter(Converter<S, T> delegate) {
            this.delegate = delegate;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public T convert(S source) {
            return delegate.convert(source);
        }

    }

    /**
     * Flow converter that consists of two steps.
     * 
     * @param <S> type of source elements
     * @param <M> type of elements when they are transferred between converters
     * @param <T> type of target elements
     */
    private static class CompositeFlowConverter<S, M, T> extends FlowConverter<S, T> {
        private final Converter<S, M> lhs;
        private final Converter<M, T> rhs;

        // Default constructor (hidden)
        private CompositeFlowConverter(Converter<S, M> lhs, Converter<M, T> rhs) {
            this.lhs = lhs;
            this.rhs = rhs;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public T convert(S source) {
            return rhs.convert(lhs.convert(source));
        }

    }

}
